/*
Copyright (c) 2013 Mihail Volkov

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#ifndef STATEMACH_H
#define STATEMACH_H

#include <list>
#include <QObject>

class SMMachine;
class SMState;

template <class SMTC1,class SMTC2>
class SMTC_And;
template <class SMTC1,class SMTC2>
class SMTC_Or;
class SMTC_Event;

/// @brief Simple machine event. Can be triggered.
/// A machine will switch automatically to new state as triggering
/// an event will hit one of current state's transitions condition.
class SMEvent: public QObject
{
	Q_OBJECT

	friend class SMTC_Event;
	SMMachine *mpMachine;
	bool mFlag;

public:
	const char *name;
	SMEvent ():
		mpMachine (NULL),
		mFlag (false)
	{}
	SMTC_Or<SMTC_Event,SMTC_Event> operator | (SMEvent &);
	SMTC_And<SMTC_Event,SMTC_Event> operator & (SMEvent &);
	template <class SMTC2> SMTC_Or<SMTC_Event,SMTC2>
	operator | (const SMTC2 &);
	template <class SMTC2> SMTC_And<SMTC_Event,SMTC2>
	operator & (const SMTC2 &);

public slots:
	/// @brief Trigger the event
	void trigger ();
	/// @brief Untrigger the event
	void untrigger ();
};

/// @brief A helper transition condition class. Don't use directly.
/// Obtain via SMEvent or expressions of them combined with & and |
/// in SMState::addTransition.
class SMTransCondition
{
	friend class SMState;
	friend class SMMachine;
	friend class SMTransition;
	friend class SMTC_Event;
	template <class SMTC1,class SMTC2>
	friend class SMTC_And;
	template <class SMTC1,class SMTC2>
	friend class SMTC_Or;

	virtual ~SMTransCondition () {}
	virtual void reset (SMMachine *) = 0;
	virtual bool check () = 0;

	virtual SMTransCondition *getDynamic () = 0;
};

/// @brief A helper transition condition class. Don't use directly.
class SMTC_Event: public SMTransCondition
{
	friend class SMMachine;
	friend class SMState;
	friend class SMEvent;
	template <class SMTC1,class SMTC2>
	friend class SMTC_And;
	template <class SMTC1,class SMTC2>
	friend class SMTC_Or;

	SMEvent &mEvent;

	void reset (SMMachine *pSM)
	{
		mEvent.mFlag = false;
		mEvent.mpMachine = pSM;
	}
	bool check () { return mEvent.mFlag; }
	SMTransCondition *getDynamic ()
	{ return new SMTC_Event (*this); }

	SMTC_Event (SMEvent &event): mEvent (event) {}
};

template <class SMTC1,class SMTC2>
class SMTC_And: public SMTransCondition
{
	friend class SMMachine;
	friend class SMState;
	friend class SMEvent;
	template <class SMTC1a,class SMTC2a>
	friend class SMTC_Or;

	SMTC1 mSubCondition1;
	SMTC2 mSubCondition2;

	void reset (SMMachine *pSM)
	{
		mSubCondition1.reset (pSM);
		mSubCondition2.reset (pSM);
	}
	bool check ()
	{
		return mSubCondition1.check () && mSubCondition2.check ();
	}

	SMTC_And (const SMTC1 &sc1,const SMTC2 &sc2):
		mSubCondition1 (sc1),
		mSubCondition2 (sc2)
	{}

public:
	SMTC_Or<SMTC_And,SMTC_Event> operator | (SMEvent &e)
	{ return SMTC_Or<SMTC_And,SMTC_Event> (*this,SMTC_Event (e)); }
	SMTC_And<SMTC_And,SMTC_Event> operator & (SMEvent &e)
	{ return SMTC_And<SMTC_And,SMTC_Event> (*this,SMTC_Event (e)); }
	template <class SMTC3> SMTC_Or<SMTC_And,SMTC3>
	operator | (const SMTC3 &);
	template <class SMTC3> SMTC_And<SMTC_And,SMTC3>
	operator & (const SMTC3 &);
	SMTransCondition *getDynamic ()
	{ return new SMTC_And (*this); }
};

inline SMTC_And<SMTC_Event,SMTC_Event>
	SMEvent::operator & (SMEvent &rhs)
{
	return SMTC_And<SMTC_Event,SMTC_Event>
		(SMTC_Event (*this),SMTC_Event (rhs));
}

template <class SMTC1,class SMTC2>
class SMTC_Or: public SMTransCondition
{
	friend class SMMachine;
	friend class SMState;
	friend class SMEvent;
	template <class SMTC1a,class SMTC2a>
	friend class SMTC_And;

	SMTC1 mSubCondition1;
	SMTC2 mSubCondition2;

	SMTC_Or (const SMTC1 &sc1,const SMTC2 &sc2):
		mSubCondition1 (sc1),
		mSubCondition2 (sc2)
	{}
	void reset (SMMachine *pSM)
	{
		mSubCondition1.reset (pSM);
		mSubCondition2.reset (pSM);
	}
	bool check ()
	{
		return mSubCondition1.check () || mSubCondition2.check ();
	}

public:
	SMTC_Or<SMTC_Or,SMTC_Event> operator | (SMEvent &e)
	{ return SMTC_Or<SMTC_Or,SMTC_Event> (*this,SMTC_Event (e)); }
	SMTC_And<SMTC_Or,SMTC_Event> operator & (SMEvent &e)
	{ return SMTC_And<SMTC_Or,SMTC_Event> (*this,SMTC_Event (e)); }
	template <class SMTC3> SMTC_Or<SMTC_Or,SMTC3>
	operator | (const SMTC3 &);
	template <class SMTC3> SMTC_And<SMTC_Or,SMTC3>
	operator & (const SMTC3 &);
	SMTransCondition *getDynamic ()
	{ return new SMTC_Or (*this); }
};

inline SMTC_Or<SMTC_Event,SMTC_Event> SMEvent::operator | (SMEvent &rhs)
{
	return SMTC_Or<SMTC_Event,SMTC_Event>
		(SMTC_Event (*this),SMTC_Event (rhs));
}

template <class SMTC1,class SMTC2>
template <class SMTC3>
inline SMTC_Or<SMTC_And<SMTC1,SMTC2>,SMTC3>
	SMTC_And<SMTC1,SMTC2>::operator | (const SMTC3 &rhs)
{
	return SMTC_Or<SMTC_And<SMTC1,SMTC2>,SMTC3> (*this,rhs);
}

template <class SMTC1,class SMTC2>
template <class SMTC3>
inline SMTC_And<SMTC_And<SMTC1,SMTC2>,SMTC3>
	SMTC_And<SMTC1,SMTC2>::operator & (const SMTC3 &rhs)
{
	return SMTC_And<SMTC_And<SMTC1,SMTC2>,SMTC3> (*this,rhs);
}

template <class SMTC1,class SMTC2>
template <class SMTC3>
inline SMTC_Or<SMTC_Or<SMTC1,SMTC2>,SMTC3>
	SMTC_Or<SMTC1,SMTC2>::operator | (const SMTC3 &rhs)
{
	return SMTC_Or<SMTC_Or<SMTC1,SMTC2>,SMTC3> (*this,rhs);
}

template <class SMTC1,class SMTC2>
template <class SMTC3>
inline SMTC_And<SMTC_Or<SMTC1,SMTC2>,SMTC3>
	SMTC_Or<SMTC1,SMTC2>::operator & (const SMTC3 &rhs)
{
	return SMTC_And<SMTC_Or<SMTC1,SMTC2>,SMTC3> (*this,rhs);
}

/// @brief State machine transition helper class. Constructed
/// indirectly (see SMState::addTransition).
class SMTransition
{
	friend class SMMachine;
	friend class SMState;

	SMTransCondition *mpCondition;
	SMState *mpNextState;

	SMTransition (SMTransCondition *pTC=NULL,SMState *pNS=NULL):
		mpCondition (pTC),
		mpNextState (pNS)
	{}
	void free ()
	{ if (mpCondition!=NULL) delete mpCondition; }

public:
	bool operator == (const SMTransition &rhs) const
	{
		return mpCondition==rhs.mpCondition &&
			mpNextState==rhs.mpNextState;
	}
};

/// @brief State machine state. Create an instance of this and fill
/// it with transitions using addTransition, then use it to set an
/// SMMachine into a state.
class SMState: public QObject
{
	Q_OBJECT

	friend class SMMachine;
	std::list<SMTransition> mTransitions;

public:
	const char *name;

	~SMState ();
	/// @brief Add a transition with condition consisting of a single
	/// event. The transition will occur as soon as the event is
	/// triggered.
	/// @param nextState State to transit from this one on condition
	/// triggered.
	/// @param cond Event to trigger to initiate this transition. Use
	/// SMEvent type variables or other stored instances.
	/// @return Transition object. Can be saved and used later to
	/// remove the transition from the state.
	SMTransition addTransition (SMState &nextState,SMEvent &cond);
	/// @brief Add a transition with condition consisting of several
	/// events combined via logical expression.
	/// @param nextState State to transit from this one on condition
	/// triggered.
	/// @param cond Condition to trigger this transition. Construct
	/// it from SMEvent variables/other stored instances using & and
	/// | operators.
	/// @return Transition object. Can be saved and used later to
	/// remove the transition from the state.
	SMTransition addTransition (SMState &nextState,
		const SMTransCondition &cond);
	/// @brief Add an immediate unconditional transition from this
	/// state to next one.
	/// @return Transition object. Can be saved and used later to
	/// remove the transition from the state.
	SMTransition addTransition (SMState &nextState);
	/// @brief Remove a transition (or do nothing if this transition
	/// is not related to this state).
	void removeTransition (const SMTransition &);

signals:
	/// @brief Emitted when the machine pointed by pInMachine changes
	/// its state to this
	void changedTo (SMMachine *pInMachine);
};

/// @brief State machine. Create an instance, set to one of SMState-s
/// using setCurrentState, then track current state via getCurrentState
/// as you trigger SMEvent-s.
class SMMachine: public QObject
{
	Q_OBJECT

	friend class SMEvent;
	SMState *mpCurrentState;
	void checkAndPossiblyTransit ();

public:
	SMMachine (SMState *pCurrentState=NULL):
		mpCurrentState (pCurrentState)
	{}
	void setCurrentState (SMState &state);
	SMState *getCurrentState ()
	{ return mpCurrentState; }

signals:
	/// @brief Emitted when the machine changes its state to one
	/// pointed by pToState
	void changedTo (SMState *pToState);
};

#endif // STATEMACH_H
